/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief JPEG encoder interface using the libjpeg (from IJG)
 * \author Antti Maula <antti.maula@tkk.fi>
 * \author Jari Saarinen
 *
 * $Id: JPEGEncoder.cpp,v 1.17 2009-09-28 08:55:34 amaula Exp $
 *
 */
#include "JPEGEncoder.hpp"
#include <assert.h>
#include <string.h>
#include <stdlib.h>

//******************************************************************************
//******************************************************************************
//#define JPEGDECODER_RGBBUFFER_BLOCKSIZE 1024
//#define JPEGENCODER_DEFAULT_QUALITY 90
//#define JPEGENCODER_OUTPUT_BUF_SIZE  (65535*10)
#define JPEGENCODER_DEFAULT_QUALITY 90
#define JPEGDECODER_RGBBUFFER_BLOCKSIZE 4096
#define JPEGENCODER_OUTPUT_BUF_SIZE  (65535*40)
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************

CJPEGEncoder::CJPEGEncoder(void) 
  : data(NULL),
    datacount(0),
    mode(0),
    iJPEGData(NULL),
    iJPEGDataSize(0),
    iJpegEnc(),
    iJPEGBuffer(),
    iJpegErr(),
    iEncoderStartJumpPoint(),
    iYUVRawDataPtr(NULL),
    iYUVRawDataSize(0),
    iGotJPEG(false)
{
  InitializeJPEGLib();
  SetYUVDataPtr(NULL,0,0);
}
//******************************************************************************

CJPEGEncoder::CJPEGEncoder(const char *aData,
			   const int aWidthPixels, const int aHeightPixels)
  : data(NULL),
    datacount(0),
    mode(0),
    iJPEGData(NULL),
    iJPEGDataSize(0),
    iJpegEnc(),
    iJPEGBuffer(),
    iJpegErr(),
    iEncoderStartJumpPoint(),
    iYUVRawDataPtr(NULL),
    iYUVRawDataSize(0),
    iGotJPEG(false)
{
  InitializeJPEGLib();
  
  // Last but not least; assign the source pointer.
  SetYUVDataPtr(aData, aWidthPixels, aHeightPixels);
}
//******************************************************************************

void CJPEGEncoder::InitializeJPEGLib(void)
{
  // Initialize the jpeg_decompress object
  jpeg_create_compress(&iJpegEnc);
  mode = JCS_YCbCr; ///< Default mode!
  // Assign the source pointer & error pointer
  iJpegEnc.dest = (struct jpeg_destination_mgr *)&iJPEGBuffer;
  iJpegEnc.err = jpeg_std_error(&iJpegErr); // Take the default at beginning.

  // Set initial settings as set on jpegInterface
  iJpegEnc.in_color_space = JCS_YCbCr; /* arbitrary guess */
  iJpegEnc.image_width = 0;
  iJpegEnc.image_height = 0;
  iJpegEnc.input_components = 3;

  jpeg_set_defaults(&iJpegEnc);
  jpeg_set_quality(&iJpegEnc,JPEGENCODER_DEFAULT_QUALITY, TRUE);	
  
  iJpegEnc.comp_info[0].h_samp_factor = 2;		
  iJpegEnc.comp_info[0].v_samp_factor = 2;
  iJpegEnc.comp_info[1].h_samp_factor = 1;	
  iJpegEnc.comp_info[1].v_samp_factor = 1;
  iJpegEnc.comp_info[2].h_samp_factor = 1;		
  iJpegEnc.comp_info[2].v_samp_factor = 1;
  iJpegEnc.raw_data_in=TRUE;
  iJpegEnc.dct_method = JDCT_FASTEST;


  // Store _dest_ callbacks to structure (Using a 'memory writer' model)
  jdest_t *dest = (jdest_t *) iJpegEnc.dest;
  dest->pub.init_destination = jpegencoder_init_destination;
  dest->pub.empty_output_buffer = jpegencoder_empty_output_buffer;
  dest->pub.term_destination = jpegencoder_term_destination;
  dest->outfile = NULL;

  // Store _err_ callbacks to structure
  iJpegEnc.err->error_exit = jpegencoder_error_exit;
  iJpegEnc.err->output_message = jpegencoder_output_message;
  iJpegEnc.err->format_message = jpegencoder_format_message;
  iJpegEnc.err->emit_message = jpegencoder_emit_message;
  iJpegEnc.client_data = this;

}
//******************************************************************************

CJPEGEncoder::~CJPEGEncoder()
{
  // Delete the YUV data (if allocated)
  if (iJPEGData) {
    delete[] iJPEGData;
    iJPEGData = NULL;
    iJPEGDataSize = 0;
  }

  // Last but not least, destroy the JPEG object.
  jpeg_destroy_compress(&iJpegEnc);
}
//******************************************************************************

bool CJPEGEncoder::SetYUVDataPtr(const char *aData,
				 const int aWidthPixels, const int aHeightPixels)
{

  // Set parameters for Encoding
  mode = JCS_YCbCr;
  iJpegEnc.in_color_space = JCS_YCbCr; /* YUV420P */
  iJpegEnc.image_width = aWidthPixels;
  iJpegEnc.image_height = aHeightPixels;
  iJpegEnc.input_components = 3;
  iJpegEnc.raw_data_in=TRUE;
  // Store dataptr & info
  iYUVRawDataPtr = (unsigned char *)aData;
  /// Does this parameter matter?
  /// The real size of YUV420P = 1.5*aWidthPixels * aHeightPixels
  //iYUVRawDataSize = ((aWidthPixels+(aWidthPixels>>1)) * aHeightPixels)+1; 
  iYUVRawDataSize = 3 * aWidthPixels * aHeightPixels; 

  // Override status; All data is new now.
  iGotJPEG = false;

  // result
  return true;
}
//******************************************************************************

bool CJPEGEncoder::SetRGBDataPtr(const char *aData,
				 const int aWidthPixels, const int aHeightPixels)
{

  // Set parameters for Encoding
  mode = JCS_RGB;
  iJpegEnc.in_color_space = JCS_RGB; /* RGB */
  iJpegEnc.image_width = aWidthPixels;
  iJpegEnc.image_height = aHeightPixels;
  iJpegEnc.input_components = 3;
  iJpegEnc.raw_data_in=FALSE;
  // Store dataptr & info
  iYUVRawDataPtr = (unsigned char *)aData;
  iYUVRawDataSize = aWidthPixels * aHeightPixels * 3;

  // Override status; All data is new now.
  iGotJPEG = false;

  // result
  return true;
}
//******************************************************************************

bool CJPEGEncoder::SetGrayscaleDataPtr(const char *aData,
				       const int aWidthPixels, const int aHeightPixels)
{

  // Set parameters for Encoding
  mode = JCS_GRAYSCALE;
  iJpegEnc.in_color_space = JCS_GRAYSCALE;
  iJpegEnc.image_width = aWidthPixels;
  iJpegEnc.image_height = aHeightPixels;
  iJpegEnc.input_components = 1;
  iJpegEnc.raw_data_in=FALSE;
  jpeg_set_defaults(&iJpegEnc);
  // Store dataptr & info
  iYUVRawDataPtr = (unsigned char *)aData;
  iYUVRawDataSize = aWidthPixels * aHeightPixels * 1;

  // Override status; All data is new now.
  iGotJPEG = false;

  // result
  return true;
}
//******************************************************************************

bool CJPEGEncoder::ReleaseYUVDataPtr(void)
{
  // Carry out the conversion by directly calling GetJPEGDataPtr()
  bool result = GetJPEGDataPtr() != NULL;
  
  // Set pointer as NULL
  SetYUVDataPtr(NULL, 0, 0);

  // Return result of conversion.
  return result;
}
//******************************************************************************

const char *CJPEGEncoder::GetJPEGDataPtr(void)
{
  const char *result = NULL;

  if (iGotJPEG) {
    result = iJPEGData;
    
  } else {
    
    if (mode == JCS_YCbCr) {
      result = CompressYUV420P();
      
    } else if(mode == JCS_RGB) {
      result = CompressRGB();
      
    } else if(mode == JCS_GRAYSCALE) {
      result = CompressGreyscale();
    
    }
  }
  return result;
}
//******************************************************************************

const char *CJPEGEncoder::CompressYUV420P(void)
{
  const char *result = NULL;
  // Now, set up jmp point
  if (setjmp(iEncoderStartJumpPoint) != 0) {
    // longjmp() got called! So invalidate the whole object.
    iGotJPEG = false;
    jpeg_abort_compress(&iJpegEnc);
    SetYUVDataPtr(NULL, 0, 0);
    
  } else if (!iGotJPEG && iYUVRawDataPtr != NULL) {
    
    JSAMPROW y[16],u[16],v[16];		
    JSAMPARRAY mcu[3];
    const int width = iJpegEnc.image_width;
    const int height = iJpegEnc.image_height;
    const int widthxheight = width*height;
    int i,j;
    unsigned char *imy,*imu,*imv;
    
    jpeg_start_compress(&iJpegEnc, TRUE);
    imy=iYUVRawDataPtr;
    imu=iYUVRawDataPtr + (widthxheight);
    imv=iYUVRawDataPtr + (widthxheight) + ((widthxheight)/4);
    
    mcu[0]=y;
    mcu[1]=u;
    mcu[2]=v;
    
    for(i=0; i < height; i += 16) {
      for(j=0; j<16; j++,imy+=width)
	y[j]=imy;
      for(j=0; j<8;j++,imu+=width/2)
	u[j]=imu;
      for(j=0; j<8;j++,imv+=width/2)
	v[j]=imv;
      jpeg_write_raw_data(&iJpegEnc, mcu, 16);
    }	
    jpeg_finish_compress(&iJpegEnc);
    
    iJPEGData = (char*)((jdest_t *)iJpegEnc.dest)->buffer;
    iJPEGDataSize = JPEGENCODER_OUTPUT_BUF_SIZE - iJpegEnc.dest->free_in_buffer;
    iGotJPEG = true;
    result = iJPEGData;

  } 

  // Now, based on the flag; set the return value
  if (iGotJPEG) {
    assert(iJPEGData != NULL);
    result = iJPEGData;

  }

  return result;
}

//******************************************************************************

const char *CJPEGEncoder::CompressRGB(void)
{
  const char *result = NULL;
  //  int times=0;
  // Now, set up jmp point
  if (setjmp(iEncoderStartJumpPoint) != 0) {
    // longjmp() got called! So invalidate the whole object.
    iGotJPEG = false;
    jpeg_abort_compress(&iJpegEnc);
    SetRGBDataPtr(NULL, 0, 0);
    
  } else if (!iGotJPEG && iYUVRawDataPtr != NULL) {
    jpeg_start_compress(&iJpegEnc, TRUE);
				
    //JSAMPROW *row_pointer; 
    unsigned char *row_ptr;
    while (iJpegEnc.next_scanline < iJpegEnc.image_height){
      row_ptr = (iYUVRawDataPtr+(iJpegEnc.next_scanline * iJpegEnc.image_width * 3));				
      jpeg_write_scanlines (&iJpegEnc, &row_ptr, 1);
    }
    jpeg_finish_compress(&iJpegEnc);
				
    iJPEGData = (char*)((jdest_t *)iJpegEnc.dest)->buffer;
    iJPEGDataSize = JPEGENCODER_OUTPUT_BUF_SIZE - iJpegEnc.dest->free_in_buffer;
    iGotJPEG = true;
    result = iJPEGData;
  } 

  // Now, based on the flag; set the return value
  if (iGotJPEG) {
    assert(iJPEGData != NULL);
    result = iJPEGData;

  }

  return result;
}
//******************************************************************************

const char *CJPEGEncoder::CompressGreyscale(void)
{
  const char *result = NULL;
  
  //  int times=0;
  // Now, set up jmp point
  if (setjmp(iEncoderStartJumpPoint) != 0) {
    // longjmp() got called! So invalidate the whole object.
    iGotJPEG = false;
    jpeg_abort_compress(&iJpegEnc);
    SetGrayscaleDataPtr(NULL, 0, 0);
    
  } else if (!iGotJPEG && iYUVRawDataPtr != NULL) {
    jpeg_start_compress(&iJpegEnc, TRUE);
    
    //JSAMPROW *row_pointer; 
    unsigned char *row_ptr;
    while (iJpegEnc.next_scanline < iJpegEnc.image_height){
      row_ptr = (iYUVRawDataPtr+(iJpegEnc.next_scanline * iJpegEnc.image_width));
      jpeg_write_scanlines (&iJpegEnc, &row_ptr, 1);
    }
    
    jpeg_finish_compress(&iJpegEnc);		
    iJPEGData = (char*)((jdest_t *)iJpegEnc.dest)->buffer;
    iJPEGDataSize = JPEGENCODER_OUTPUT_BUF_SIZE - iJpegEnc.dest->free_in_buffer;
    iGotJPEG = true;
    result = iJPEGData;
  } 

  // Now, based on the flag; set the return value
  if (iGotJPEG) {
    assert(iJPEGData != NULL);
    result = iJPEGData;
    
  }

  return result;
}
//******************************************************************************

bool CJPEGEncoder::GetJPEGDataCopy(char *aDataPtr, const int aMaxBufSize)
{
  bool result = false;
  
  // First, carry out reading the data.
  const char *jpegdata = GetJPEGDataPtr();
  if (jpegdata && aMaxBufSize >= iJPEGDataSize) {
    // All OK, just copy the data to caller.
    memcpy(aDataPtr, iJPEGData, iJPEGDataSize);
    result = true;
  }

  return result;
}
//******************************************************************************

int CJPEGEncoder::GetJPEGDataSize(void)
{
  if (GetJPEGDataPtr() != NULL) 
    return iJPEGDataSize;
  else
    return -1;
}
//******************************************************************************

bool CJPEGEncoder::SetJPEGQuality(const int aQuality)
{ 
  // Set the JPEG quality
  // FIXME: No value validity checks made!
  jpeg_set_quality(&iJpegEnc, aQuality, TRUE);	
  return true;
}
//******************************************************************************

size_t CJPEGEncoder::jpg_compress(unsigned char *image, int width, int height)
{
  SetYUVDataPtr((const char *)image, width, height);
  data = (unsigned char *)GetJPEGDataPtr();
  datacount = (data!=NULL) ? iJPEGDataSize : 0;
  return datacount;
}
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************

boolean CJPEGEncoder::jpegencoder_empty_output_buffer (j_compress_ptr cinfo)
{
  return 0;
}
//******************************************************************************

void CJPEGEncoder::jpegencoder_term_destination (j_compress_ptr cinfo)
{
}
//******************************************************************************

void CJPEGEncoder::jpegencoder_init_destination (j_compress_ptr cinfo)
{
  // Set up decent pointer to class
  //CJPEGEncoder *that = ((CJPEGEncoder *)cinfo->client_data);

  // Create cinfo ptr
  jdest_t *dest = (jdest_t *) cinfo->dest;
  
  /* Allocate the output buffer --- it will be released when done with image */
  if(dest->buffer==NULL) {
    dest->buffer = (JOCTET *)malloc(JPEGENCODER_OUTPUT_BUF_SIZE * sizeof(JOCTET));
    assert(dest->buffer != NULL);
  }
    
  dest->pub.next_output_byte = dest->buffer;
  dest->pub.free_in_buffer = JPEGENCODER_OUTPUT_BUF_SIZE;

}
//******************************************************************************

void CJPEGEncoder::jpegencoder_error_exit (j_common_ptr cinfo) 
{
  // Set up decent pointer to class
  CJPEGEncoder *that = ((CJPEGEncoder *)cinfo->client_data);

	
  //  printf("error_exit\n");


  // Perform longjmp() to generate higher level error.
  longjmp(that->iEncoderStartJumpPoint, 1);
} 
//******************************************************************************

void CJPEGEncoder::jpegencoder_output_message (j_common_ptr cinfo)
{
  // Set up decent pointer to class
  //  CJPEGEncoder *that = ((CJPEGEncoder *)cinfo->client_data);
  //printf("output_message\n");
}
//******************************************************************************

void CJPEGEncoder::jpegencoder_format_message (j_common_ptr cinfo, char * buffer)
{
  // Set up decent pointer to class
  // CJPEGEncoder *that = ((CJPEGEncoder *)cinfo->client_data);
  //printf("format_message\n");

}
//******************************************************************************

void CJPEGEncoder::jpegencoder_emit_message (j_common_ptr cinfo, int msg_level)
{
  // Set up decent pointer to class
  // CJPEGEncoder *that = ((CJPEGEncoder *)cinfo->client_data);
  //printf("emit_message\n");

}
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
